import json
from decimal import Decimal
from typing import Union, List, Dict
from inspect import isfunction, ismethod, isclass

from virtual_operator_service_base.utils.constant.constant import NULL
from virtual_operator_service_base.utils.logger_util import logger
from virtual_operator_service_base.utils.mixin import MixinResponsePostProcessor
from virtual_operator_service_base.utils.object_util import dict_2_obj, obj_2_dict

CACHE_FIELDS_KEY = "CACHE_FIELDS"
CLIENT_FIELDS_KEY = "CLIENT_FIELDS"
DEST_ALL = 0
DEST_CACHE = 1
DEST_CLIENT = 2


def to_obj(data: dict, obj_class: object):
    """
        dict转object对象
    """
    return from_json(to_json(data), obj_class)


def load_obj(json_str: str, obj_class: object):
    """
        将json字符串转换为指定类
    """
    return dict_2_obj(json.loads(json_str), obj_class)


def dupm_obj(obj: object):
    """
        将类对象输出字典在转换为json字符串
    """
    return json.dumps(obj_2_dict(obj))


# 注意循环引用的问题
def to_json(obj, nullable=False, dest=DEST_ALL, post_process_response_kargs: dict = {}) -> str:
    if obj is None:
        return NULL if nullable else None
    return json.dumps(_to_json_mediate(obj, dest, post_process_response_kargs))


def to_json_debug(obj, nullable=False, dest=DEST_ALL, post_process_response_kargs={}) -> str:
    if obj is None:
        logger.warn("to_json_debug none")
        return NULL if nullable else None
    return json.dumps(_to_json_mediate(obj, dest, post_process_response_kargs))


def _to_json_mediate(obj, dest=DEST_ALL, post_process_response_kargs={}) -> Union[dict, list, str, int, float, bool, None]:
    if obj is None:
        return None
    if isfunction(obj) or ismethod(obj) or isclass(obj):
        raise TypeError
    if isinstance(obj, Decimal):
        obj = float(obj)
    if _isprimitive(obj):
        return obj
    if _islist(obj):
        ret = []
        for item in obj:
            if item is not None:
                ret.append(_to_json_mediate(item, dest, post_process_response_kargs))
        return ret
    if _isdict(obj):
        ret = {}
        for key in obj:
            if obj[key] is not None:
                ret[key] = _to_json_mediate(obj[key], dest, post_process_response_kargs)
        return ret

    # 普通对象
    if post_process_response_kargs and isinstance(obj, MixinResponsePostProcessor):
        obj.post_process_response(**post_process_response_kargs)
    ret = {}
    props = _get_properties(obj, dest)
    for prop in props:
        attr = getattr(obj, prop)
        if isinstance(attr, Decimal):
            attr = float(attr)
        if _isprimitive(attr):
            ret[prop] = attr
        else:
            ret[prop] = _to_json_mediate(attr, dest, post_process_response_kargs)
    return ret


def from_json(jsonstr: str, cls=None, nullable=False):
    if jsonstr is None:
        return None
    if nullable and jsonstr == NULL:
        return None

    obj = json.loads(jsonstr)
    if not cls or cls is object:
        return obj
    return _wrap(obj, cls)


def wrap(item, cls):
    return _wrap(item, cls)


def _wrap(item, cls):
    if item is None or not cls or cls is object:
        return item
    if _isprimitive(item):
        return _wrap_primitive(item, cls)
    if _islist(item):
        return _wrap_list(item, _get_list_item_cls(cls))
    if _isdict(item):
        return _wrap_dict(item, cls)
    else:
        raise TypeError


def _wrap_list(items: list, itemcls):
    if items is None:
        return None
    if len(items) == 0 or itemcls is None or itemcls is object:
        return items
    ret = []
    for item in items:
        ret.append(_wrap(item, itemcls))
    return ret


def _wrap_dict(item: dict, cls):
    # do nothing
    if cls is None or cls is object or cls is dict:
        return item
    # Dict类型处理
    name = "__args__"
    if hasattr(cls, name) and cls == Dict[getattr(cls, name)]:
        # JSON key只能为字符串类型
        key_cls = getattr(cls, name)[0]
        value_cls = getattr(cls, name)[1]
        ret = {}
        for key in item:
            key_obj = _wrap(key, key_cls)
            value_obj = _wrap(item[key], value_cls)
            ret[key_obj] = value_obj
        return ret
    # 普通对象类型
    else:
        ret = cls()
        for key in item:
            if not _has_property(cls, key):
                continue
            if item[key] is None:
                setattr(ret, key, None)
            else:
                prop_cls = _get_property_cls(cls, key)
                prop_val = _wrap(item[key], prop_cls)
                setattr(ret, key, prop_val)
        return ret


def _wrap_primitive(item, cls):
    # if cls is not None and cls is not object and type(item) != cls:
    #     raise TypeError
    if cls is Decimal and isinstance(item, float):
        item = Decimal(str(item))
    return item


def _islist(item) -> bool:
    return isinstance(item, (list, tuple))


def _isprimitive(item) -> bool:
    return isinstance(item, (str, int, float, bool))


def _isdict(item) -> bool:
    return isinstance(item, dict)


def _get_list_item_cls(cls):
    if cls == list:
        return None
    name = "__args__"
    if not hasattr(cls, name) or cls != List[getattr(cls, name)]:
        raise TypeError
    return getattr(cls, name)[0]


def _get_property_cls(cls, name):
    if hasattr(cls, name):
        return type(getattr(cls, name))
    if not hasattr(cls, '__annotations__'):
        return None
    prop2cls = getattr(cls, '__annotations__')
    return prop2cls[name] if name in prop2cls else None


def _has_property(cls, name):
    if hasattr(cls, name):
        return True
    if not hasattr(cls, '__annotations__'):
        return False
    return name in getattr(cls, '__annotations__')


def _get_properties(obj, dest) -> List[str]:
    props = []
    for name in dir(obj):
        # 这三种类型的属性不处理
        if name.startswith("_") or name == CACHE_FIELDS_KEY or name == CLIENT_FIELDS_KEY:
            continue
        attr = getattr(obj, name)
        if attr is None or isfunction(attr) or ismethod(attr) or isclass(attr):
            continue
        props.append(name)
    # 移除只返回给客户端的字段
    if dest == DEST_CACHE:
        if hasattr(obj, CLIENT_FIELDS_KEY):
            for field in getattr(obj, CLIENT_FIELDS_KEY):
                if field in props:
                    props.remove(field)
    # 移除只用于缓存的字段
    if dest == DEST_CLIENT:
        if hasattr(obj, CACHE_FIELDS_KEY):
            for field in getattr(obj, CACHE_FIELDS_KEY):
                if field in props:
                    props.remove(field)
    return props
